/*
 *
 * Allow lookup from many maps.
 * does not allow null value.
 *
 * Note a typical usecase will be to call addMap() on a frozen map followed by addMap() on an modifyable map.
 *
 * The lower immutable map will usually contain data used in many places where a small modification is required.
 * The higher map will be used to override the lower map in those few places. Since the lower map is inmmutable, the
 * same map can be used every where to share common data without reallocating and reinitializing another map.
 *
 * TODO: resolve how to return the keySets either TreeSet or HashSet.
 * TODO: implement CascadingSet
 * 
 *
 */
package org.yagnus.langutils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author hc.busy
 */
public class CascadingMap<K, V> implements Map<K, V> {

    ArrayList<Map<K, V>> maps;

    public CascadingMap() {
        maps = new ArrayList<Map<K, V>>();
    }

    public void addMap(Map<K, V> min) {
        maps.add(min);
    }

    public V get(Object key) {
        for (int i = maps.size() - 1; i > -1; --i) {
            V v = maps.get(i).get(key);
            if (v != null) {
                return v;
            }
        }
        return null;
    }

    public boolean containsKey(Object key) {
        for (int i = maps.size() - 1; i > -1; --i) {
            if (maps.get(i).containsKey(key)) {
                return true;
            }
        }
        return false;
    }

    public boolean containsValue(Object v) {
        for (int i = maps.size() - 1; i > -1; --i) {
            if (maps.get(i).containsValue(v)) {
                return true;
            }
        }
        return false;
    }

    public V put(K key, V value) {
        V ret = get(key);
        maps.get(maps.size() - 1).put(key, value);
        return ret;
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        Map<K, V> map = maps.get(size() - 1);
        map.putAll(m);
    }

    /**
     * BUG: resolve if we need hashSet or treeSe
     * @return
     */
    public Set<K> keySet() {
        Set<K> ret = new HashSet<K>();
        for (int i = 0; i < maps.size(); ++i) {
            ret.addAll(maps.get(i).keySet());
        }
        return ret;
    }

    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> ret = new HashSet<Entry<K, V>>();
        for (int i = 0; i < maps.size(); ++i) {
            ret.addAll(maps.get(i).entrySet());
        }
        return ret;
    }

    public Collection<V> values() {
        Collection<V> ret = new ArrayList<V>();
        for (int i = 0; i < maps.size(); ++i) {
            ret.addAll(maps.get(i).values());
        }
        return ret;
    }

    /**
     * todo: optimize this at construction/update time.
     * @return
     */
    public int size() {
        return keySet().size();
    }

    /**
     *
     * returns all keys in the cascading map, returning the last one that wasn't null (or null if they were all null)
     * @param key
     * @return
     */
    public V remove(Object key) {
        V ret = null;

        for (Map<K, V> m : maps) {
            V tr = m.remove(key);
            if (tr != null) {
                ret = tr;
            }
        }
        return ret;
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (o == null || !(o instanceof CascadingMap)) {
            return false;
        }

        CascadingMap O = (CascadingMap) o;
        return maps.equals(O.maps);
    }

    public int hashCode() {
        return maps.hashCode();
    }

    public void clear() {
        for (Map<K, V> m : maps) {
            m.clear();
        }
    }

    public boolean isEmpty() {
        return size() == 0;
    }
}
